#include <iostream>
#include <mutex>
#include <memory>
#include <type_traits>
#include <cstdint>

#include "cppdemangle.h"

using Mutex_Guard = std::lock_guard<std::mutex>;
struct Widget {};
enum GlobalColor : std::uint8_t { RED, GREEN, BLUE };

int f1(std::shared_ptr<Widget> spw)
{
    std::cout << "call f1" << "  " << __PRETTY_FUNCTION__ << std::endl;
    return 1;
}

double f2(std::unique_ptr<Widget> upw)
{
    std::cout << "call f2" << "  " << __PRETTY_FUNCTION__ << std::endl;
    return 1.0f;
}

bool f3(Widget* pw)
{
    std::cout << "call f3" << "  " << __PRETTY_FUNCTION__ << std::endl;
    return true;
}

template<
    typename FuncType,
    typename MutexType,
    typename PtrType>
decltype(auto) lockAndCall(FuncType func, MutexType& mutex, PtrType ptr)
{
    std::cout << "lockAndCall" << "  " << __PRETTY_FUNCTION__ << std::endl;
    Mutex_Guard guard(mutex);
    return func(ptr);
}

// Alias templates for convenience -> if don't support c++14, just support c++11
template <class T> 
using remove_const_t = typename std::remove_const<T>::type;

template <class T> 
using remove_reference_t = typename std::remove_reference<T>::type;

template <class T> 
using add_lvalue_reference_t = typename std::add_lvalue_reference<T>::type; 

template <typename T>
using underlying_type_t = typename std::underlying_type<T>::type;

// using 
using int_type = remove_const_t<const int>;
using int_ref_type = remove_const_t<const int>&;

static int_type i = 0;
static int_ref_type j = i;

void func()
{
    {
        enum Color { RED, GREEN, BLUE };
        auto color = RED; // non-scoped enum
        std::cout << hamster_cpp_demangle::demangle<decltype(color)>() << std::endl;
    }
}

void test_underlying_type()
{
    enum Color { RED, GREEN, BLUE };
    auto color = RED;
    std::cout << "underlying type of Color: " << hamster_cpp_demangle::demangle<underlying_type_t<Color>>() << std::endl;
}

template<typename enumrator_type>
constexpr std::underlying_type_t<enumrator_type> 
to_underlying_type(enumrator_type scoped_enum)
{
    return static_cast<std::underlying_type_t<enumrator_type>>(scoped_enum);
}
// After C++ 14, we can use decltype(auto) to simplify the return type
// for this case, decltype(auto) can be depreciated to auto
template<typename enumrator_type>
constexpr decltype(auto)
to_underlying_type_2(enumrator_type scoped_enum)
{
    return static_cast<std::underlying_type_t<enumrator_type>>(scoped_enum);
}

void test_scoped_enum_transform()
{
    enum class UserInfoFields { uiName, uiEmail, uiReputation };
    using UserInfo =                
    std::tuple<std::string,     
               std::string,     
               std::size_t> ;   
    UserInfo uInfo = { "Alice", "alice@example.com", 100 };       

    auto val = std::get<static_cast<std::size_t>(UserInfoFields::uiName)>(uInfo);
    std::cout << "val: " << val << std::endl;
    auto val2 = std::get<to_underlying_type(UserInfoFields::uiReputation)>(uInfo);
    std::cout << "val2: " << val2 << std::endl;
    auto val3 = std::get<to_underlying_type_2(UserInfoFields::uiEmail)>(uInfo);
    std::cout << "val3: " << val3 << std::endl;
}

int main()
{
    std::mutex f1m, f2m, f3m;

    // ---------------------
    // {
    //     Mutex_Guard f1g(f1m);
    //     auto res = f1(0);
    // }
    // {
    //     Mutex_Guard f2g(f2m);
    //     auto res = f2(NULL);
    // }
    // {
    //     Mutex_Guard f3g(f3m);
    //     auto res = f3(nullptr);
    // }
    // ------------ run OK --------------

    std::cout << sizeof(Widget) << std::endl;
    // auto res1 = lockAndCall(f1, f1m, 0);
    // auto res2 = lockAndCall(f2, f2m, NULL);
    auto res3 = lockAndCall(f3, f3m, nullptr);

    {
        // test non-scoped enum
        enum Color { RED, GREEN, BLUE };
        auto color = RED; // non-scoped enum
        // auto RED = false; // wrong ! because RED is a non-scoped enum
        // RED reclared as different kind of entity
        std::cout << hamster_cpp_demangle::demangle<decltype(color)>() << std::endl;
        auto res = color < 10;
    }

    {
        // test scoped enum
        enum class Color { RED, GREEN, BLUE };
        auto color = Color::RED; // scoped enum
        auto RED = false; // OK, because RED is a scoped enum
        std::cout << hamster_cpp_demangle::demangle<decltype(color)>() << std::endl;
        std::cout << hamster_cpp_demangle::demangle<decltype(RED)>() << std::endl;
        // auto res = color < 10; // Wrong
    }
    // the global enum type 
    std::cout << "the global enum type: " << hamster_cpp_demangle::demangle<decltype(GlobalColor::BLUE)>() << std::endl;

    func();

    {
        // forward declaration
        enum class Color; // OK
        enum Color2 : std::uint8_t; // OK, after indicate underlying type, unscoped enum is supported forward declaration

        // test enum class size
        enum class Color { RED, GREEN, BLUE };
        auto a = Color::RED;
        std::cout << "enum class Color size: " << sizeof(a) << std::endl; 

        // indicate underlying type for unscoped enum
        enum Color2 : std::uint8_t { RED2, GREEN2, BLUE2 };
        auto b = RED2;
        std::cout << "enum Color2 size: " << sizeof(b) << std::endl; 
    }

    test_underlying_type();

    test_scoped_enum_transform();

    return 0;
}